package id.co.rho.scoring.cid.server.service.impl;

import id.co.rho.scoring.cid.client.model.CriteriaTree;
import id.co.rho.scoring.cid.domain.Criteria;
import id.co.rho.scoring.cid.domain.CriteriaType;
import id.co.rho.scoring.cid.domain.Model;
import id.co.rho.scoring.cid.server.dao.GenericDao;
import id.co.rho.scoring.cid.server.model.QuestionAnswer;
import id.co.rho.scoring.cid.server.service.UploadAnswerService;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

@Service
@Transactional
public class UploadAnswerServiceImpl implements UploadAnswerService {

	private GenericDao genericDao;
	Map<Long, CriteriaTree> mapOfCriteria = new HashMap<Long, CriteriaTree>();

	@Autowired
	public void setGenericDao(GenericDao genericDao) {
		this.genericDao = genericDao;
	}

	@Override
	public String uploadAnswer(FileInputStream fileInputStream, String modelId)
			throws ParserConfigurationException, SAXException, IOException,
			IllegalAccessException, InvocationTargetException, TransformerException {

		Model model = genericDao.find(Model.class, Long.parseLong(modelId));

		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();

		Document doc = dBuilder.parse(fileInputStream);
		doc.getDocumentElement().normalize();

		NodeList nList = doc.getElementsByTagName("question");

		List<QuestionAnswer> questionAnswers = new ArrayList<QuestionAnswer>();
		for (int temp = 0; temp < nList.getLength(); temp++) {

			Node nNode = nList.item(temp);
			String questionNumber = nNode.getAttributes()
					.getNamedItem("number").getNodeValue();
			QuestionAnswer questionAnswer = new QuestionAnswer();
			questionAnswer.setQuestionNumber(questionNumber);
			List<String> answerList = new ArrayList<String>();
			NodeList children = nNode.getChildNodes();
			for (int temp2 = 0; temp2 < children.getLength(); temp2++) {
				Node childNode = children.item(temp2);
				if (childNode.getNodeType() == Node.ELEMENT_NODE) {
					Element eElement = (Element) childNode;
					String childValue = eElement.getTextContent();
					answerList.add(childValue);
				}

			}
			questionAnswer.setAnswerNumber(answerList);
			questionAnswers.add(questionAnswer);
		}

		for (Criteria criteria : model.getCriteries()) {
			criteria.setResultValue(this.calculateResultValue(criteria,
					questionAnswers));
		}

		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

		Document document = docBuilder.newDocument();
		Element scoringElement = document.createElement("scoring");
		document.appendChild(scoringElement);

		// model information
		Element modelElement = document.createElement("model");

		Element modelName = document.createElement("name");
		modelName.appendChild(document.createTextNode(model.getModelName()));
		modelElement.appendChild(modelName);

		Element typeModel = document.createElement("type");
		typeModel.appendChild(document.createTextNode(model
				.getTypeModelConfig()));
		modelElement.appendChild(typeModel);

		Element revision = document.createElement("revision");
		revision.appendChild(document.createTextNode(Integer.toString(model
				.getRevision())));

		for (Criteria criteria : model.getCriteries()) {
			Element criteriaElement = document.createElement("criteria");

			Attr criteriaNumber = document.createAttribute("number");
			criteriaNumber.appendChild(document.createTextNode(criteria
					.getCriteriaNumber()));
			criteriaElement.setAttributeNode(criteriaNumber);

			Element criteriaName = document.createElement("name");
			criteriaName.appendChild(document.createTextNode(criteria
					.getCriteriaName()));
			criteriaElement.appendChild(criteriaName);

			Element criteriaRank = document.createElement("rank");
			if (criteria.getCriteriaRank() != null) {
				criteriaRank.appendChild(document.createTextNode(Double
						.toString(criteria.getCriteriaRank())));
			}
			criteriaElement.appendChild(criteriaRank);

			Element criteriaType = document.createElement("type");
			if (criteria.getCriteriaType() != null) {
				criteriaType.appendChild(document.createTextNode(criteria
						.getCriteriaType().toString()));
			}
			criteriaElement.appendChild(criteriaType);

			Element criteriaWeight = document.createElement("weight");
			criteriaWeight.appendChild(document.createTextNode(Double
					.toString(criteria.getCriteriaWeight())));
			criteriaElement.appendChild(criteriaWeight);

			Element resultValue = document.createElement("resultvalue");
			if (criteria.getResultValue() != null) {
				resultValue.appendChild(document.createTextNode(Double
						.toString(criteria.getResultValue())));
			}
			criteriaElement.appendChild(resultValue);

			generateChildElement(document, criteriaElement, criteria);

			modelElement.appendChild(criteriaElement);

		}

		scoringElement.appendChild(modelElement);

		// write the content into xml file
		TransformerFactory transformerFactory = TransformerFactory
				.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		DOMSource source = new DOMSource(document);
		Writer outWriter = new StringWriter();  
		StreamResult result = new StreamResult(outWriter);

		transformer.transform(source, result);
		StringWriter sw = (StringWriter) result.getWriter();

		return sw.getBuffer().toString();
	}

	private void generateChildElement(Document document,
			Element criteriaElement, Criteria criteria) {

		for (Criteria child : criteria.getChild()) {
			Element criteriaElement2 = document.createElement("criteria");

			Attr criteriaNumber = document.createAttribute("number");
			criteriaNumber.appendChild(document.createTextNode(child
					.getCriteriaNumber()));
			criteriaElement2.setAttributeNode(criteriaNumber);

			Element criteriaName = document.createElement("name");
			criteriaName.appendChild(document.createTextNode(child
					.getCriteriaName()));
			criteriaElement2.appendChild(criteriaName);

			Element criteriaRank = document.createElement("rank");
			if (child.getCriteriaRank() != null) {
				criteriaRank.appendChild(document.createTextNode(Double
						.toString(child.getCriteriaRank())));
			}
			criteriaElement2.appendChild(criteriaRank);

			Element criteriaType = document.createElement("type");
			if (child.getCriteriaType() != null) {
				criteriaType.appendChild(document.createTextNode(child
						.getCriteriaType().toString()));
			}
			criteriaElement2.appendChild(criteriaType);

			Element criteriaWeight = document.createElement("weight");
			if (child.getCriteriaWeight() != null) {
				criteriaWeight.appendChild(document.createTextNode(Double
						.toString(child.getCriteriaWeight())));
			}
			criteriaElement2.appendChild(criteriaWeight);

			Element resultValue = document.createElement("resultvalue");
			if (child.getResultValue() != null) {
				resultValue.appendChild(document.createTextNode(Double
						.toString(child.getResultValue())));
			}
			criteriaElement2.appendChild(resultValue);

			generateChildElement(document, criteriaElement2, child);
			criteriaElement.appendChild(criteriaElement2);
		}

	}

	public Double calculateResultValue(Criteria criteria,
			List<QuestionAnswer> questionAnswers) {
		Double result = new Double(0);
		String criteriaType = "empty";
		if (criteria.getCriteriaType() != null) {
			criteriaType = criteria.getCriteriaType().toString();
		}
		if (!criteriaType.equalsIgnoreCase(CriteriaType.QUESTION.toString())
				&& !criteriaType.equalsIgnoreCase(CriteriaType.ANSWER
						.toString())) {
			for (Criteria child : criteria.getChild()) {
				result = result + criteria.getCriteriaWeight()
						* calculateResultValue(child, questionAnswers);
				criteria.setResultValue(result);
			}
		} else if (criteriaType.equalsIgnoreCase(CriteriaType.QUESTION
				.toString())) {
			for (QuestionAnswer questionAnswer : questionAnswers) {
				if (criteria.getCriteriaNumber().equalsIgnoreCase(
						questionAnswer.getQuestionNumber())) {
					for (Criteria child : criteria.getChild()) {
						for (String answer : questionAnswer.getAnswerNumber()) {
							if (child.getCriteriaNumber().equalsIgnoreCase(
									answer)) {
								result = result + child.getCriteriaWeight();
							}
						}
					}

				}
			}
			criteria.setResultValue(result);
		}
		return result;
	}

}
